﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BricksProblemSolver
{
    public class SolutionsCountSolver
    {
        private Dictionary<IBrickRow, IBrickRow[]> _brickRowNotMatchDic;
        private int _maxHeight;
        private ILog _log;

        /// <summary>
        /// Cache the solved count of a specified row on a specified height
        /// </summary>
        private ConcurrentDictionary<Tuple<IBrickRow, int>, long> _solvedCountCache = new ConcurrentDictionary<Tuple<IBrickRow, int>, long>();

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="brickRowNotMatchDic">Dictionary to keep brick rows mapping. Key - a brick row; Value - an array of brick rows that are not on the same gap of the key.</param>
        /// <param name="maxHeight">The row count of the wall</param>
        /// <param name="log">Interface for the log</param>
        public SolutionsCountSolver(Dictionary<IBrickRow, IBrickRow[]> brickRowNotMatchDic, int maxHeight, ILog log)
        {
            _brickRowNotMatchDic = brickRowNotMatchDic;
            _maxHeight = maxHeight;
            _log = log;
        }

        public long Solve()
        {
#if DEBUG
            int index = 0;
#endif
            return _brickRowNotMatchDic.Keys
                .Partition(100)
                .AsParallel()
                //.WithDegreeOfParallelism(Environment.ProcessorCount * 2)
                .Sum(collection =>
                    collection.Sum(x =>
                    {
                        long count = GetSolutionCount(0, x);
#if DEBUG
                            _log.Write("Got {0} count for row #{1}.", count, Interlocked.Increment(ref index));
#endif
                            return count;
                    }));
        }

        private long GetSolutionCount(int rowIndex,
            IBrickRow currentBrickRow)
        {
            var possibleRows = _brickRowNotMatchDic[currentBrickRow];
            if (!possibleRows.Any())
            {
                return 0;
            }
            if (rowIndex == _maxHeight - 1)
            {
                return 1;
            }
            else
            {
                long count = 0;
                for (int i = 0; i < possibleRows.Length; ++i)
                {
                    long subCount;
                    //Try to use cached count first to save performance.
                    if (!_solvedCountCache.TryGetValue(Tuple.Create(possibleRows[i], rowIndex + 1), out subCount))
                    {
                        subCount = GetSolutionCount(rowIndex + 1, possibleRows[i]);
                        if (rowIndex > 0)
                        {
#if DEBUG
                            var existingOne =
#endif
                                    _solvedCountCache.GetOrAdd(Tuple.Create(possibleRows[i], rowIndex + 1), subCount);
#if DEBUG
                            if (existingOne != subCount)
                            {
                                _log.Write("Indicate mismatch count for row '{0}' of height index '{1}'. {2} vs {3}.", possibleRows[i], rowIndex + 1, subCount, existingOne);

                            }
#endif
                        }
                    }
                    count += subCount;
                }
                return count;
            }
        }
    }
}
